<template>
  <div ref="dropdownRef" :class="ns.b('dropdown')">
    <div v-if="$slots.header" :class="ns.be('dropdown', 'header')">
      <slot name="header" />
    </div>
    <el-scrollbar v-show="options.length > 0 && !loading" :id="contentId" ref="scrollbarRef" tag="ul"
      :wrap-class="ns.be('dropdown', 'wrap')" :view-class="ns.be('dropdown', 'list')" role="listbox"
      :aria-label="ariaLabel" aria-orientation="vertical">
      <li v-for="(item, index) in options" :id="`${contentId}-${index}`" ref="optionRefs"
        :class="optionkls(item, index)" :key="index" role="option" @mousemove="handleMouseEnter(index)"
        :aria-disabled="item.disabled || disabled || undefined" :aria-selected="hoveringIndex === index"
        @click.stop="handleSelect(item)">
        <slot name="label" :item="item" :index="index">
          <span>{{ item.label ?? item.value }}</span>
        </slot>
      </li>
    </el-scrollbar>
    <div v-if="loading" :class="ns.be('dropdown', 'loading')">
      <slot name="loading"> Loading </slot>
    </div>
    <div v-if="$slots.footer" :class="ns.be('dropdown', 'footer')">
      <slot name="footer" />
    </div>
  </div>
</template>

<script setup lang="ts" name="ElMentionDropdown">
import { ref, watch, computed, nextTick } from 'vue'
import { ElScrollbar, useNamespace } from 'element-plus'
import { type MentionOption } from './types'
import { isClient } from '@vueuse/core'
import { mentionDropdownEmits, mentionDropdownProps } from './MentionDropdown'

const emit = defineEmits(mentionDropdownEmits)
const props = defineProps(mentionDropdownProps)

const ns = useNamespace('mention')

const hoveringIndex = ref(-1)

const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const optionRefs = ref<HTMLElement[]>()
const dropdownRef = ref<HTMLElement>()

const optionkls = (item: MentionOption, index: number) => [
  ns.be('dropdown', 'item'),
  ns.is('hovering', hoveringIndex.value === index),
  ns.is('disabled', item.disabled || props.disabled),
]
const handleMouseEnter = (index: number) => {
  hoveringIndex.value = index
}
const filteredAllDisabled = computed(
  () => props.disabled || props.options.every((item) => item.disabled)
)

const hoverOption = computed(() => props.options[hoveringIndex.value])

const selectHoverOption = () => {
  if (!hoverOption.value) return
  emit('select', hoverOption.value)
}

const handleSelect = (item: MentionOption) => {
  if (item.disabled || props.disabled) {
    return;
  }
  emit('select', item)
}

const navigateOptions = (direction: 'next' | 'prev') => {
  const { options } = props
  if (options.length === 0 || filteredAllDisabled.value) return

  if (direction === 'next') {
    hoveringIndex.value++
    if (hoveringIndex.value === options.length) {
      hoveringIndex.value = 0
    }
  } else if (direction === 'prev') {
    hoveringIndex.value--
    if (hoveringIndex.value < 0) {
      hoveringIndex.value = options.length - 1
    }
  }
  const option = options[hoveringIndex.value]
  if (option.disabled) {
    navigateOptions(direction)
    return
  }
  nextTick(() => scrollToOption(option))
}

const scrollToOption = (option: MentionOption) => {
  const { options } = props

  const index = options.findIndex((item) => item.value === option.value)
  const target = optionRefs.value?.[index]

  if (target) {
    const menu = dropdownRef.value?.querySelector?.(
      `.${ns.be('dropdown', 'wrap')}`
    )
    if (menu) {
      scrollIntoView(menu as HTMLElement, target)
    }
  }
  scrollbarRef.value?.handleScroll()
}

const resetHoveringIndex = () => {
  if (filteredAllDisabled.value || props.options.length === 0) {
    hoveringIndex.value = -1
  } else {
    hoveringIndex.value = 0
  }
}

const scrollIntoView = function (
  container: HTMLElement,
  selected: HTMLElement
): void {
  if (!isClient) return

  if (!selected) {
    container.scrollTop = 0
    return
  }

  const offsetParents: HTMLElement[] = []
  let pointer = selected.offsetParent
  while (
    pointer !== null &&
    container !== pointer &&
    container.contains(pointer)
  ) {
    offsetParents.push(pointer as HTMLElement)
    pointer = (pointer as HTMLElement).offsetParent
  }
  const top =
    selected.offsetTop +
    offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0)
  const bottom = top + selected.offsetHeight
  const viewRectTop = container.scrollTop
  const viewRectBottom = viewRectTop + container.clientHeight

  if (top < viewRectTop) {
    container.scrollTop = top
  } else if (bottom > viewRectBottom) {
    container.scrollTop = bottom - container.clientHeight
  }
}

watch(() => props.options, resetHoveringIndex, {
  immediate: true,
})

defineExpose({
  hoveringIndex,
  navigateOptions,
  selectHoverOption,
  hoverOption,
})
</script>

<style scoped></style>
